package com.orange.delivery.presentation;

import java.util.Properties;

import org.apache.wicket.Session;
import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
import org.apache.wicket.authroles.authorization.strategies.role.Roles;
import org.apache.wicket.injection.Injector;
import org.apache.wicket.request.Request;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;

/**
 * Hold application session, authenticate wicket user and map spring security roles to wicket roles
 * 
 */
public class WicketSession extends AuthenticatedWebSession {

	private static final long serialVersionUID = -7843719714249664102L;
	private static final Logger LOG = LoggerFactory.getLogger(WicketSession.class.getName());

	// Authenticated user and roles are available in session
	private User user;
	private Roles roles;

	@SpringBean(name = "authenticationManager")
	private AuthenticationManager authenticationManager;

	@SpringBean(name = "applicationProperties")
	private Properties applicationProperties;

	public WicketSession(Request request) {
		super(request);
		LOG.debug("creating authenticated wicket session");
		// @SpringBean injection is done manually because this class isn't a
		// Component
		Injector.get().inject(this);
		if (authenticationManager == null) {
			throw new IllegalStateException("An authenticationManager is required.");
		}
		if (applicationProperties == null) {
			throw new IllegalStateException("applicationProperties Spring bean not found");
		}

		if ("authentication_gassi".equals(applicationProperties.getProperty("spring.profiles.active"))) {
			// we're using "authentication_gassi" spring profile
			// authentication is already done
			LOG.info("using authentication_gassi profile");
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			LOG.debug("Authentication : {}", authentication);
			populateSessionWithUserAndRoles(authentication);
		} else {
			// we're using "authentication_form" spring profile
			// session will be populated in authenticate() method
			LOG.info("using authentication_form profile");
		}
	}

	/**
	 * static method to get the session from everywhere without cast
	 */
	public static WicketSession get() {
		return (WicketSession) Session.get();
	}

	/**
	 * Attempts to authenticate a user that has provided the given username and password.
	 * 
	 * @param username
	 *            current username
	 * @param password
	 *            current password
	 * @return <code>true</code> if authentication succeeds, <code>false</code> otherwise
	 */
	@Override
	public boolean authenticate(String username, String password) {

		LOG.debug("Authenticating user {}", username);

		boolean authenticated = false;
		try {
			Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username,
					password));
			SecurityContextHolder.getContext().setAuthentication(authentication);
			authenticated = authentication.isAuthenticated();
			populateSessionWithUserAndRoles(authentication);
		} catch (AuthenticationException e) {
			LOG.warn("User '{}' failed to login", username, e);
			authenticated = false;
		}
		return authenticated;
	}

	/**
	 * Store current user and it's roles in session
	 * 
	 * @param authentication
	 */
	private void populateSessionWithUserAndRoles(Authentication authentication) {
		if (authentication != null && authentication.isAuthenticated()) {
			Object principal = authentication.getPrincipal();
			LOG.debug("User principal : {}", principal);
			if (principal instanceof User) {
				// we store Spring Security User but it should be a dedicated
				// User object with more specific getters
				user = (User) authentication.getPrincipal();
				// Convert Spring Security roles to Wicket roles
				roles = new Roles();
				for (GrantedAuthority authority : authentication.getAuthorities()) {
					roles.add(authority.getAuthority());
				}
				LOG.debug("User roles : {}", roles);
			} else {
				LOG.error("principal {} should be a User", principal);
			}
		}
	}

	/**
	 * @return connected user
	 */
	public User getUser() {
		return user;
	}

	/**
	 * @return user's roles
	 */
	@Override
	public Roles getRoles() {
		return roles;
	}

	/**
	 * @return applicationProperties
	 */
	public Properties getApplicationProperties() {
		return applicationProperties;
	}

}
